﻿using System;
using System.Collections.Generic;
using Pfz.Collections;
using Pfz.Threading;

#if SILVERLIGHT
#else
using Pfz.DynamicObjects;
#endif

namespace Pfz.Extensions
{
	/// <summary>
	/// Adds some methods to the Dictionary generic class.
	/// </summary>
	public static class PfzDictionaryExtensions
	{
		/// <summary>
		/// Gets a value by its key or, if it doesn't exist, returns the default
		/// value for TValue.
		/// </summary>
		public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
		{
			if (dictionary == null)
				throw new ArgumentNullException("dictionary");

			TValue result;
			dictionary.TryGetValue(key, out result);
			return result;
		}
		
		/// <summary>
		/// Tries to get a value by its key. If it doesn't exist, creates a new
		/// one, adds it to the dicionary and returns it.
		/// </summary>
		public static TValue GetOrCreateValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
		where
			TValue: new()
		{
			if (dictionary == null)
				throw new ArgumentNullException("dictionary");

			TValue result;
			
			if (!dictionary.TryGetValue(key, out result))
			{
				result = new TValue();
				dictionary.Add(key, result);
			}
			
			return result;
		}

		/// <summary>
		/// Gets a value for a key or creates a new one using the given createValue delegate.
		/// Uses a read lock first, then a upgradeable lock and, if the value is still not there,
		/// a write lock.
		/// </summary>
		public static TValue GetOrCreateValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, ref YieldReaderWriterLockSlim readerWriterLock, TKey key, Func<TKey, TValue> createValue)
		{
			TValue result;
			readerWriterLock.EnterReadLock();
			try
			{
				if (dictionary.TryGetValue(key, out result))
					return result;
			}
			finally
			{
				readerWriterLock.ExitReadLock();
			}

			bool upgraded = false;
			readerWriterLock.EnterUpgradeableLock();
			try
			{
				if (dictionary.TryGetValue(key, out result))
					return result;

				result = createValue(key);
				readerWriterLock.UpgradeToWriteLock();
				upgraded = true;
				dictionary.Add(key, result);
			}
			finally
			{
				if (upgraded)
					readerWriterLock.ExitUpgradedLock();
				else
					readerWriterLock.ExitUpgradeableLock();
			}

			return result;
		}
		/* if you are using the extension method, then use the Slim dictionary.
		 * public static TValue GetOrCreateValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, YieldReaderWriterLock readerWriterLock, TKey key, Func<TKey, TValue> createValue)
		{
			TValue result;
			using(readerWriterLock.ReadLock())
				if (dictionary.TryGetValue(key, out result))
					return result;

			using(var upgradeableLock = readerWriterLock.UpgradeableLock())
			{
				if (dictionary.TryGetValue(key, out result))
					return result;

				result = createValue(key);
				upgradeableLock.Upgrade();
				dictionary.Add(key, result);
			}

			return result;
		}*/
		
		#if SILVERLIGHT
		#else
			/// <summary>
			/// Gets a read-only wrapper over this dictionary.
			/// </summary>
			public static IReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> modifiableDictionary)
			{
				return ReadOnlyDictionary<TKey, TValue>.Create(modifiableDictionary);
			}
		#endif
	}
}
